package cn.jasonone.jfx.context;

import cn.hutool.core.util.StrUtil;
import cn.jasonone.jfx.annotations.JFXController;
import cn.jasonone.jfx.config.properties.JfxProperties;
import cn.jasonone.jfx.context.BaseController;
import cn.jasonone.jfx.factory.ResourceBundleFactory;
import cn.jasonone.jfx.factory.StyleSheetInterceptor;
import cn.jasonone.jfx.mananger.BindManager;
import cn.jasonone.jfx.mananger.ControllerManager;
import cn.jasonone.jfx.model.ControllerDefinition;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;

/**
 * @author xujie
 * @version 1.0
 * @date 2021/12/21 10:01
 */
@Slf4j
@Component("controllerManager")
public class JFXContextHolder implements ControllerManager {

    private static final String FXML_SUFFIX = ".fxml";
    private static final CharSequence CONTROLLER_SUFFIX = "Controller";

    @Resource
    private ApplicationContext applicationContext;
    @Resource
    private ResourceBundleFactory resourceBundleFactory;
    @Resource
    private JfxProperties jfxProperties;
    @Resource
    private BindManager bindManager;
    @Getter
    private List<StyleSheetInterceptor> styleSheetInterceptors = new CopyOnWriteArrayList<>();
    private Map<Parent, Object> controllerMap = new ConcurrentHashMap<>();
    private Map<Object, Parent> paneMap = new ConcurrentHashMap<>();
    private Map<Class, ControllerDefinition> controllerDefinitionMap = new ConcurrentHashMap<>();

    @PostConstruct
    public void init() throws IOException {
        initControllerDefinition();
        initStyleSheetInterceptor();
    }


    @Override
    public <C> C getController(Parent parent) {
        return (C) controllerMap.get(parent);
    }

    @Override
    public <C> Set<C> getControllers(Scene scene) {
        return (Set<C>)controllerMap.entrySet().stream().filter(e->e.getKey().getScene() == scene).map(Map.Entry::getValue).collect(Collectors.toSet());
    }

    /**
     * 初始化样式拦截器
     */
    private void initStyleSheetInterceptor() {
        Map<String, StyleSheetInterceptor> styleSheetInterceptorMap = applicationContext.getBeansOfType(StyleSheetInterceptor.class);
        for (StyleSheetInterceptor styleSheetInterceptor : styleSheetInterceptorMap.values()) {
            this.styleSheetInterceptors.add(styleSheetInterceptor);
            log.debug("注册样式拦截器:{}", styleSheetInterceptor);
        }
    }

    /**
     * 初始化处理器定义信息
     */
    private void initControllerDefinition() throws IOException {
        Map<String, Object> controllerMap = applicationContext.getBeansWithAnnotation(JFXController.class);
        Set<Map.Entry<String, Object>> entries = controllerMap.entrySet();
        for (Map.Entry<String, Object> entry : entries) {
            String name = entry.getKey();
            Object controller = entry.getValue();
            Class type = controller.getClass();
            JFXController jfxController = AnnotationUtils.getAnnotation(type, JFXController.class);
            ControllerDefinition controllerDefinition = new ControllerDefinition();
            controllerDefinition.setName(name);
            controllerDefinition.setType(type);
            if (!jfxController.resourceBundleName().isEmpty()) {
                controllerDefinition.setResourceBundle(resourceBundleFactory.getResourceBundle(jfxController.resourceBundleName()));
            }
            //region 解析FXML文件路径
            String fxmlPath = jfxController.fxmlPath();
            if (StrUtil.isBlank(fxmlPath)) {
                String viewName = type.getSimpleName();
                if (StrUtil.endWith(viewName, CONTROLLER_SUFFIX)) {
                    viewName = StrUtil.subBefore(viewName, CONTROLLER_SUFFIX, true);
                }
                fxmlPath = viewName + FXML_SUFFIX;
            }
            if (!StrUtil.endWithIgnoreCase(fxmlPath, FXML_SUFFIX)) {
                fxmlPath += FXML_SUFFIX;
            }
            String fxmlRootPath = jfxProperties.getFxmlRootPath();
            org.springframework.core.io.Resource pathRelative = applicationContext.getResource("classpath:" + Paths.get(fxmlRootPath, fxmlPath));
            if (pathRelative.exists()) {
                controllerDefinition.setFxmlPath(pathRelative.getURL());
            }
            //endregion
            // 注册控制器
            controllerDefinitionMap.put(type, controllerDefinition);
            log.debug("注册控制器:{}", controllerDefinition);
        }
    }


    @Override
    public void bindController(Parent pane, Object controller) {
        controllerMap.put(pane, controller);
        paneMap.put(controller, pane);
        bindManager.bind(pane, controller);
    }

    @Override
    public boolean hasController(Parent pane) {
        return controllerMap.containsKey(pane);
    }


    /**
     * 根据控制器类型获取控制器定义对象
     *
     * @param controllerClass
     * @return
     */
    public ControllerDefinition getControllerDefinition(Class<? extends BaseController> controllerClass) {
        return controllerDefinitionMap.get(controllerClass);
    }

    /**
     * 递归查询指定节点信息,返回第一个找到的节点
     * @param root
     * @param selector
     * @param <N>
     * @return
     */
    public static <N extends Node> N lookup(Node root, String selector) {
        Set<N> nodes = lookupAll(root, selector);
        if (nodes.isEmpty()) {
            return null;
        }
        return nodes.iterator().next();
    }

    /**
     * 递归查询指定节点信息
     *
     * @param root
     * @param selector
     * @param <N>
     * @return
     */
    public static <N extends Node> Set<N> lookupAll(Node root, String selector) {
        Set<N> list = new LinkedHashSet<>();
        Set<Node> nodes = root.lookupAll(selector);
        if (nodes.isEmpty()) {
            return Collections.emptySet();
        } else {
            for (Node node : nodes) {
                if (node != root) {
                    list.add((N) node);
                    list.addAll(lookupAll(node, selector));
                }
            }
        }
        return list;
    }
}
